﻿using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

public class JwtSettings
{
    public string SecretKey { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public int ExpireMinutes { get; set; } = 30;
}

public class UserPayload
{
    public int id { get; set; }
    public string code { get; set; }
}

public class JwtHelper
{
    private readonly JwtSettings _settings;
    private readonly SymmetricSecurityKey _securityKey;

    public JwtHelper(JwtSettings settings)
    {
        _settings = settings ?? throw new ArgumentNullException(nameof(settings));
        _securityKey = new SymmetricSecurityKey(Convert.FromBase64String(_settings.SecretKey));
    }
    public JwtHelper(IConfiguration configuration)
    {
        _settings = new JwtSettings
        {
            SecretKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(configuration["JWT:secret"])), // 示例随机密钥
            Issuer = configuration["JWT:issuer"],
            Audience = configuration["JWT:audience"],
            ExpireMinutes = 60 * 24 * 7
        };
        _securityKey = new SymmetricSecurityKey(Convert.FromBase64String(_settings.SecretKey));
    }

    /// <summary>
    /// 生成JWT Token
    /// </summary>
    /// <param name="payload">需要转换为Claims的自定义对象</param>
    /// <param name="additionalClaims">额外自定义Claims</param>
    public string CreateToken<T>(T payload, IEnumerable<Claim> additionalClaims = null)
    {
        var claims = ConvertObjectToClaims(payload);

        if (additionalClaims != null)
        {
            claims.AddRange(additionalClaims);
        }

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.UtcNow.AddMinutes(_settings.ExpireMinutes),
            Issuer = _settings.Issuer,
            Audience = _settings.Audience,
            SigningCredentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256Signature)
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var securityToken = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(securityToken);
    }

    /// <summary>
    /// 解析验证Token并获取原始Claims
    /// </summary>
    public IEnumerable<Claim> ParseToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();

        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _settings.Issuer,
                ValidAudience = _settings.Audience,
                IssuerSigningKey = _securityKey
            }, out _);

            var jwtToken = tokenHandler.ReadJwtToken(token);
            return jwtToken.Claims;
        }
        catch (SecurityTokenException ex)
        {
            throw new Exception("Token validation failed", ex);
        }
    }

    /// <summary>
    /// 解析Token并将Claims转换为指定类型对象
    /// </summary>
    public T ParseToken<T>(string token) where T : new()
    {
        var claims = ParseToken(token);
        return ConvertClaimsToObject<T>(claims);
    }

    private List<Claim> ConvertObjectToClaims<T>(T obj)
    {
        var claims = new List<Claim>();
        if (obj == null) return claims;

        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var property in properties)
        {
            var value = property.GetValue(obj)?.ToString();
            if (!string.IsNullOrEmpty(value))
            {
                claims.Add(new Claim(property.Name, value));
            }
        }
        return claims;
    }

    private T ConvertClaimsToObject<T>(IEnumerable<Claim> claims) where T : new()
    {
        var obj = new T();
        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

        var claimDict = new Dictionary<string, Claim>(claims.Select(c => new KeyValuePair<string, Claim>(c.Type, c)));

        foreach (var property in properties)
        {
            if (claimDict.TryGetValue(property.Name, out var claim))
            {
                var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
                if (!string.IsNullOrEmpty(claim.Value))
                {
                    var value = Convert.ChangeType(claim.Value, targetType);
                    property.SetValue(obj, value);
                }
            }
        }
        return obj;
    }
}